#include <stdio.h>
#include <string.h>
#include "src/lta.h"
#include "src/ltbase.h"
#include "src/ltgen_gens.h"
#include "src/ltlog.h"

lt_UsingType(lt, USize);

static int
Generate(char const* point) {
    ltlog_Log1(Info, "generate", "point", StringQuote(point));

    for (USize i = 0; ltgen_gGens[i].name != NULL; ++i) {
        if (strcmp(point, ltgen_gGens[i].name) == 0)
            return ltgen_gGens[i].gen();
    }

    ltlog_Log1(Error, "undefined generate point", "point name",
               StringQuote(point));
    return 1;
}

/**找到一行中的ltgen声明，返回生成点的名称。找不到的情况下返回NULL。*/
static char const*
FindGenPoint(char* line) {
    char const* prefix = "/* ltgen ";
    char const* suffix = " */";
    size_t prefix_len = strlen(prefix);

    char* start = strstr(line, prefix);
    char* end = strstr(line, suffix);

    if (start == NULL || end == NULL)
        return NULL;

    start[0] = 0;
    start[prefix_len - 1] = 0;
    end[0] = 0;

    return start + prefix_len;
}

static char const* gEnd = "/* ltgen_End */";

static int
GenerateFile(char const* src) {
    int status;

    FILE* reader = fopen(src, "r");
    if (reader == NULL) {
        status = 1;
        goto end;
    }

    ltlog_Log1(Info, "run ltgen", "file path", StringQuote(src));

    char line[65536];
    while (fgets(line, sizeof(line), reader) != NULL) {
        char const* point = FindGenPoint(line);
        char const* line_prefix = line;

        if (point == NULL) {
            printf("%s", line);
            continue;
        }

        printf("%s/* ltgen %s */\n", line_prefix, point);
        if (Generate(point) != 0) {
            status = 1;
            goto end;
        }
        printf("%s%s\n", line_prefix, gEnd);

        /*丢弃从前生成的代码。*/

        while (true) {
            if (fgets(line, sizeof(line), reader) == NULL) {
                ltlog_Log0(Error,
                           "reach end of file before end of ltgen block");
                status = 1;
                goto end;
            }

            if (strstr(line, gEnd) != NULL)
                break;
        }
    }

    status = 0;
end:
    if (reader != NULL)
        fclose(reader);

    return status;
}

int
CollectGens(char const* src, lta_StringArr* gens) {
    int status;

    FILE* reader = fopen(src, "r");
    if (reader == NULL) {
        status = 1;
        goto end;
    }

    bool first = true;
    char line[65536];

    while (fgets(line, sizeof(line), reader) != NULL) {
        if (first) {
            if (strstr(line, "/* Use ltgen */") == NULL) {
                status = 0;
                goto end;
            }
            first = false;
        }

        char const* point = FindGenPoint(line);
        if (point == NULL)
            continue;

        ltlog_Log2(Info, "collect gen", "file path", StringQuote(src),
                   "gen point", StringQuote(point));

        lta_Append(*gens, strdup(point));
    }

    status = 0;
end:
    if (reader != NULL)
        fclose(reader);
    return status;
}

static int
GenerateGens(lta_StringArr const* gens) {
    printf("/* Generated by ltgen collect-gens, DO NOT EDIT. */\n"
           "#include \"src/ltgen_gens.h\"\n"
           "#include <stddef.h>\n"
           "#include \"src/ltgen.h\"\n"
           "\n");

    lta_ForEach(i, *gens) {
        char const* point = gens->data[i];
        printf("ltgen_Generator(%s);\n", point);
    }

    printf("\n"
           "ltgen_Gen ltgen_gGens[] = {\n");

    lta_ForEach(i, *gens) {
        char const* point = gens->data[i];
        printf("    {.name = \"%s\", .gen = ltgen_Generator_Name(%s)},\n",
               point, point);
    }

    printf("    {.name = NULL, .gen = NULL},\n"
           "};\n");

    return 0;
}

int
main(int argc, char const* argv[]) {
    if (argc < 2) {
        ltlog_Log0(Error, "usage: ltgen generate     SRC...");
        ltlog_Log0(Error, "       ltgen collect-gens SRC...");
        return 1;
    }

    char const* cmd = argv[1];

    if (strcmp(cmd, "generate") == 0) {
        for (int i = 2; i < argc; ++i) {
            if (GenerateFile(argv[i]) != 0)
                return 1;
        }
        return 0;
    }

    if (strcmp(cmd, "collect-gens") == 0) {
        lta_StringArr gens = {0};

        for (int i = 2; i < argc; ++i) {
            if (CollectGens(argv[i], &gens) != 0) {
                ltlog_Log1(Error, "failed to collect gen", "file path",
                           StringQuote(argv[i]));
                return 1;
            }
        }

        return GenerateGens(&gens);
    }

    ltlog_Log1(Error, "undefined command", "command", StringQuote(argv[1]));

    return 0;
}
